
const events = require("events");
const {spawn} = require('child_process');
const schedule = require('node-schedule');

const MBI_EVENTS = {
    CONNECTED:'connected',
}
const REGEX = {
    TRID : /TRID: '(\d+)'/m,
    RX_PACKAGES : /^rx_packets=(\d*)/,
    TX_PACKAGES : /^tx_packets=(\d*)/,
    RX_BYTES : /^rx_bytes=(\d*)/,
    TX_BYTES : /^tx_bytes=(\d*)/,
    CONN_TIME : /^connected_time=(\d*)/,
    SIGNAL : /signal=(-*\d*)/,
    LINESPLITE : /[\r\n]/gm,
    SUBSCRIBER : /Ready state: '(initialized)'/m,
    REG_STATE : /Register state: '(home)'/m,
    REG_MODE : /Register mode: '(\w+)'/m,
    AVAIL_DATA_CLASS : /Available data classes: '(\w+)'/m,
    PROVIDER_NAME : /Provider name: '(\w+)'/m,

    PACKETSERVICE_STATE : /Packet service state: '(\w+)'/m,
    UPLINK_SPEED : /Uplink speed: '(\d+) bps'/m,
    DOWNLINK_SPEED : /Downlink speed: '(\d+) bps'/m,

    NET_ACTIVE_STATE : /Activation state: '(\w+)'/m,
    NET_IP_TYPE : /IP type: '(\w+)'/m,
    NET_CONTEXT_TYPE : /Context type: '(\w+)'/m,

    IPV4_IP: /IP \[\d\]: '(\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3})\/(\d{1,3})'/m,
    IPV4_GATEWAY: /Gateway: '(\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3})'/m,
    IPV4_DNS:/DNS \[\d\]: '(\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3})'/m,

}
class MbimCliParser extends events.EventEmitter {

    constructor(){
        super();
    }

    _parseTRID(data){
        const match = data.match(REGEX.TRID);
        console.log("parse TRID:",match);
        if(match){
            return parseInt(match[1]);
        }else{
            return null;
        }
    }
    _parseSubscriberStatus(data){
        const match = data.match(REGEX.SUBSCRIBER);
        console.log("parse SubscriberStatus:",match);
        if(match){
            return true;
        }else{
            return null;
        }
    }
    _parseStringValue(data,regex){
        const match = data.match(regex);
        console.log("parse string:",match);
        if(match){
            let res = []; 
            for( let i = 1; i< match.length;i++){
                res.push(match[i]);
            }
            return res.length>1?res:res[0];
        }else{
            return null;
        }
    }
    _parseStringArrayValue(data,regex){
        const match = data.matchAll(regex);
        if(match){
            const matchArr = Array.from(match);
            let res = []; 
            for(let j=0;j<matchArr.length;j++){
                let subRs = [];
                let subMatch = matchArr[j];
                for( let i = 1; i< subMatch.length;i++){
                    subRs.push(subMatch[i]);
                }
                res.push(subRs.length>1?subRs:subRs[0]);
            }
            return res;
        }else{
            return null;
        }
    }
    _parseBoolValue(data,regex){
        const match = data.match(regex);
        console.log("parse bool:",match);
        if(match){
            return true;
        }else{
            return null;
        }
    }
    
    /**
     * Querying subscriber ready status 
     * Cmd: mbimcli -d /dev/cdc-wdm0 --query-subscriber-ready-status --no-close 
     * 返回数据：
     * {
     *    trid : 数字
     *    inited： 布尔值，是否初始化成功
     * }
     */
    async query_subscriber_ready_status (){

        const self = this;
        let subscriber =  {inited:false,trid:-1};
        return new Promise((resolve, reject) => {
            self.run('mbimcli',['-d','/dev/cdc-wdm0','--query-subscriber-ready-status','--no-close'],{
                onData : (data)=>{
                    console.log("ondata:",data.toString());
                    const trid = self._parseTRID(data.toString());
                    const inited = self._parseSubscriberStatus(data.toString());
                    subscriber.trid = trid ? trid : subscriber.trid;
                    subscriber.inited = inited ? inited : subscriber.inited;
                },
                onClose : (code,data)=>{
                    console.log("onclose:",subscriber,code);
                    if (code !== 0){
                        reject({code,data});
                    }else{
                        resolve({code:0,...subscriber});
                    }
                },
            })
        });
    }

    /**
     * 2. Querying registration state
     *  'mbimcli -d /dev/cdc-wdm0 --query-registration-state --no-open=3 --no-close '...
     */
    async query_registration_state (transid) {
        const self = this;
        let registration =  {registerState:'none',registerMode :'',availableDataClasses:'',
        providerName:"",trid:-1};
        return new Promise((resolve, reject) => {
            self.run('mbimcli',['-d', '/dev/cdc-wdm0','--query-registration-state','--no-open='+transid,'--no-close'],{
                onData : (data)=>{
                    const strData = data.toString();
                    console.log("ondata:",strData);
                    const regState = self._parseStringValue(strData,REGEX.REG_STATE);
                    const regMode = self._parseStringValue(strData,REGEX.REG_STATE);
                    const provederName = self._parseStringValue(strData,REGEX.REG_STATE);
                    const trid = self._parseTRID(strData);
                    const dataClasses = self._parseStringValue(strData,REGEX.AVAIL_DATA_CLASS);
                    registration.availableDataClasses = dataClasses?dataClasses:registration.availableDataClasses;
                    registration.registerState = regState?regState:registration.registerState;
                    registration.registerMode = regMode?regMode:registration.registerMode;
                    registration.providerName = provederName?providerName:registration.providerName;
                    registration.trid = trid?trid:registration.trid;

                },
                onClose : (code,data)=>{
                    console.log("onclose:",registration,code);
                    if (code !== 0){
                        reject({code,data});
                    }else{
                        resolve({code:0,...registration});
                    }
                },
            })
        });
    }
    /**
      * 3. Attaching to packet service with 
      * 'mbimcli -d /dev/cdc-wdm0 --attach-packet-service --no-open=4 --no-close '...
      * 
     */
    async attach_packet_service (transid) {
        let self = this;
        let packet_service =  {packetServiceState:'none',availableDataClasses:'',
        uplinkSpeed:-1,downLinkSpeed:-1,trid:-1};
        return new Promise((resolve, reject) => {
            self.run('mbimcli',['-d','/dev/cdc-wdm0','--attach-packet-service','--no-open='+transid,'--no-close'],{
                onData : (data)=>{
                    const strData = data.toString();
                    console.log("ondata:",strData);
                    const packetServiceState = self._parseStringValue(strData,REGEX.PACKETSERVICE_STATE);
                    const uplinkSpeed = self._parseStringValue(strData,REGEX.UPLINK_SPEED);
                    const downLinkSpeed = self._parseStringValue(strData,REGEX.DOWNLINK_SPEED);
                    const trid = self._parseTRID(strData);
                    const dataClasses = self._parseStringValue(strData,REGEX.AVAIL_DATA_CLASS);
                    packet_service.availableDataClasses = dataClasses?dataClasses:packet_service.availableDataClasses;
                    packet_service.packetServiceState = packetServiceState?packetServiceState:packet_service.packetServiceState;
                    packet_service.uplinkSpeed = uplinkSpeed?uplinkSpeed:packet_service.uplinkSpeed;
                    packet_service.downLinkSpeed = downLinkSpeed?downLinkSpeed:packet_service.downLinkSpeed;
                    packet_service.trid = trid?trid:packet_service.trid;
                },
                onClose : (code,data)=>{
                    console.log("onclose:",packet_service,code);
                    if (code !== 0){
                        reject({code,data});
                    }else{
                        resolve({code:0,...packet_service});
                    }
                }
            })
        });
    }
    /**
      * 4. Starting network with 
      * 'mbimcli -d /dev/cdc-wdm0 --connect=apn='cmnet' --no-open=5 --no-close '...
      * 
     */
    async start_network(transid) {
        let self = this;
        let networkLink =  {activationState:'',ipType:'',contextType:'',ip:'',subnet:'',gateway:'',dns:[]};

        return new Promise((resolve, reject) => {
            self.run('mbimcli',['-d','/dev/cdc-wdm0','--connect=apn=\'cmnet\'','--no-open='+transid,'--no-close'],{
                onData : (data)=>{
                    const strData = data.toString();
                    console.log("ondata:",strData);
                    const activationState = self._parseStringValue(strData,REGEX.NET_ACTIVE_STATE);
                    const ipType = self._parseStringValue(strData,REGEX.NET_IP_TYPE);
                    const contextType = self._parseStringValue(strData,REGEX.NET_CONTEXT_TYPE);
                    const ip = self._parseStringValue(strData,REGEX.IPV4_IP);
                    const gateway = self._parseStringValue(strData,REGEX.IPV4_GATEWAY);
                    const dns = self._parseStringArrayValue(strData,REGEX.IPV4_DNS);

                    networkLink.activationState = activationState?activationState:networkLink.activationState;
                    networkLink.ipType = ipType?ipType:networkLink.ipType;
                    networkLink.contextType = contextType?contextType:networkLink.contextType;
                    networkLink.ip = ip?ip[0]:networkLink.ip;
                    networkLink.subnet = ip?ip[1]:networkLink.subnet;
                    networkLink.gateway = gateway?gateway:networkLink.gateway;
                    networkLink.dns = dns?dns:networkLink.dns;
                    
                },
                onClose : (code,data)=>{
                    console.log("onclose:",networkLink,code);
                    if (code !== 0){
                        reject({code,data});
                    }else{
                        resolve({code:0,...networkLink});
                    }
                }
            })
        });
    }
    async queryconnectStatus(transid){
        let self = this;
        let connectStatus = {};
        return new Promise((resolve, reject) => {
            self.run('mbimcli',['-d','/dev/cdc-wdm0','--query-radio-state','--no-open='+transid,'--no-close'],{
                onData : (data)=>{
                    console.log(data.toString());
                },
                onClose : (code,data)=>{
                    console.log("onclose:",connectStatus,code);
                    if (code !== 0){
                        reject({code,data});
                    }else{
                        resolve({code:0,...connectStatus});
                    }
                }
            })
        });
    }

    async disConnect(transid){
        let self = this;
        let disconnectState = {};
        return new Promise((resolve, reject) => {
            self.run('mbimcli',['-d','/dev/cdc-wdm0','--disconnect','--no-open='+transid],{
                onData : (data)=>{
                    console.log(data.toString());
                    //Network stopped successfully
                },
                onClose : (code,data)=>{
                    console.log("onclose:",disconnectState,code);
                    if (code !== 0){
                        reject({code,data});
                    }else{
                        resolve({code:0,...disconnectState});
                    }
                }
            })
        });
    }

    run(cmd,params,{onData,onError,onClose}){
        const mbim_cli = spawn(cmd,params,{detached : false});
        mbim_cli.stdout.on('data', (data) => {
          onData && onData(data);
        });
        
        mbim_cli.stderr.on("data",(data)=>{
           onError && onError(data);
        });
        
        mbim_cli.on("close",(code,signal) =>{
           onClose && onClose(code,signal);
        });
    }    
}

class MbimCli extends events.EventEmitter{
    constructor(){
        super();
        this.interval = null;
        this.mbimCliParser = new MbimCliParser();
        //Register connected event
        this.mbimCliParser.on(MBI_EVENTS.CONNECTED,(sta)=>{
            this.emit(MBI_EVENTS.CONNECTED,sta);
        });

        console.log("init schedule");
        //每分钟的第1秒定时执行一次:
        this.job = schedule.scheduleJob('1 * * * * *',()=>{
            console.log('scheduleCronstyle:' + new Date());
        }); 
    }
    _parseCmd(param){
        param && param.mbinetworkParser.queryconnectStatus();
    }

    connectNetwork(){
        /**
         * 1. Querying subscriber ready status 
         * 'mbimcli -d /dev/cdc-wdm0 --query-subscriber-ready-status --no-close '...
         * [/dev/cdc-wdm0] Subscriber ready status retrieved: Ready state: 'initialized' Subscriber ID: '460077011945596' SIM ICCID: '898600A001210006201' Ready info: 'none' Telephone numbers: (0) 'unknown' [/dev/cdc-wdm0] Session not closed: TRID: '3'
         * Saving state at /tmp/mbim-network-state-cdc-wdm0... (TRID: 3)
         * 
         */
        this.query_subscriber_ready_status();
        /**
         * 2. Querying registration state
         * 'mbimcli -d /dev/cdc-wdm0 --query-registration-state --no-open=3 --no-close '...
         * [/dev/cdc-wdm0] Registration status: Network error: 'unknown' Register state: 'home' Register mode: 'automatic' Available data classes: 'lte' Current cellular class: 'gsm' Provider ID: '46000' Provider name: 'CMCC' Roaming text: 'unknown' Registration flags: 'packet-service-automatic-attach' [/dev/cdc-wdm0] Session not closed: TRID: '4'
         * Saving state at /tmp/mbim-network-state-cdc-wdm0... (TRID: 4)
       */
        this.query_registration_state();
        /**
         * 3. Attaching to packet service with 
         * 'mbimcli -d /dev/cdc-wdm0 --attach-packet-service --no-open=4 --no-close '...
         * Saving state at /tmp/mbim-network-state-cdc-wdm0... (TRID: 5)
         */
        this.attach_packet_service();
        /** 
         * 4. Starting network with 
         * 'mbimcli -d /dev/cdc-wdm0 --connect=apn='cmnet' --no-open=5 --no-close '...
         * Network started successfully
         * Saving state at /tmp/mbim-network-state-cdc-wdm0... (TRID: 7)
         */
         this.start_network();

    }
    disconnect(){
        clearInterval(this.interval);
    }
}

module.exports = { MbimCli,MBI_EVENTS,MbimCliParser};
